package net.cyue.easyconfiguration;

import net.cyue.easyconfiguration.annotation.ConfigurationItem;
import net.cyue.easyconfiguration.api.IConfigurationResource;
import net.cyue.easyconfiguration.node.NodeType;
import net.cyue.easyconfiguration.node.definition.AbstractNodeDefinition;
import net.cyue.easyconfiguration.node.util.NodeValueConverter;
import net.cyue.easyconfiguration.node.util.NodeValueReader;
import net.cyue.util.ReflectUtil;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class ConfigurationLoader {

    public static Map<String, Object> load(
        ConfigurationDefinition definition,
        IConfigurationResource configurationResource
    ) {
        Map<String, Object> configurationMap = new HashMap<>();
        for (AbstractNodeDefinition nodeDefinition : definition.getConfigItemDefinitionSet()) {
            Object value = NodeValueReader.getProcessedNodeValue(
                configurationResource,
                nodeDefinition
            );
            configurationMap.put(
                nodeDefinition.getName(),
                value
            );
        }
        return configurationMap;
    }

    public static <T> T load(
        Class<T> clazz,
        ConfigurationDefinition definition,
        IConfigurationResource configurationResource
    ) throws NoSuchMethodException, InvocationTargetException {
        Map<String, Object> configurationMap = load(definition, configurationResource);
        NodeType clzType = NodeType.fromClass(clazz);
        if (Map.class.isAssignableFrom(clazz)) {
            return (T) configurationMap;
        } else if (Set.class.isAssignableFrom(clazz)) {
            return (T) new HashSet<>(configurationMap.values());
        } else if (List.class.isAssignableFrom(clazz)) {
            return (T) new ArrayList<>(configurationMap.values());
        } else if (clazz.isArray()) {
            return (T) configurationMap.values().toArray(new Object[0]);
        } else if (clzType != NodeType.TABLE && clzType != NodeType.ANY) {
            return null;
        }

        T configuration = ReflectUtil.createInstance(clazz);
        for (Field f : ReflectUtil.getAllFields(clazz)) {
            String nodeName;
            ConfigurationItem configurationItem = f.getAnnotation(ConfigurationItem.class);
            if (configurationItem == null) {
                nodeName = f.getName();
            } else {
                nodeName = configurationItem.value();
            }
            Object value = configurationMap.get(nodeName);
            if (value == null) {
                continue;
            }

            Class<?>[] fieldParameterClasses = ReflectUtil.getTypeArgumentClass(f.getGenericType());
            if (fieldParameterClasses == null) {
                fieldParameterClasses = new Class<?>[]{Object.class};
            }

            if (f.getType().isArray()) {
                Object[] arr = NodeValueConverter.convertValue2Array(value, f.getType().getComponentType());
                ReflectUtil.setFieldValue(configuration, f, arr);
            } else if (
                Set.class.isAssignableFrom(f.getType())
            ) {
                ReflectUtil.setFieldValue(
                    configuration,
                    f,
                    NodeValueConverter.convertValue2Set(
                        value,
                        fieldParameterClasses[0]
                    )
                );
            } else if (
                List.class.isAssignableFrom(f.getType())
            ) {
                ReflectUtil.setFieldValue(
                    configuration,
                    f,
                    NodeValueConverter.convertValue2List(
                        value,
                        fieldParameterClasses[0]
                    )
                );
            } else if (
                Map.class.isAssignableFrom(f.getType())
            ) {
                Class<?> parameterClass =
                    fieldParameterClasses.length > 1 ? fieldParameterClasses[1] : Object.class;
                ReflectUtil.setFieldValue(
                    configuration,
                    f,
                    NodeValueConverter.convertValue2Map(value, parameterClass)
                );
            } else {
                ReflectUtil.setFieldValue(configuration, f, value);
            }
        }
        return configuration;
    }
}
